/*
 *
 *   Copyright (C) 2005 by Raymond Huang
 *   plushuang at users.sourceforge.net
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  ---
 *
 *  In addition, as a special exception, the copyright holders give
 *  permission to link the code of portions of this program with the
 *  OpenSSL library under certain conditions as described in each
 *  individual source file, and distribute linked combinations
 *  including the two.
 *  You must obey the GNU Lesser General Public License in all respects
 *  for all of the code used other than OpenSSL.  If you modify
 *  file(s) with this exception, you may extend this exception to your
 *  version of the file(s), but you are not obligated to do so.  If you
 *  do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source
 *  files in the program, then also delete it here.
 *
 */

#include <string.h>
#include <stdlib.h>
#include <urlglib/url_info.h>
#include <urlglib/urlglib_util.h>
#include <urlglib/download_node.h>

static void download_node_finalize (DownloadNode* node);

DownloadNode* download_node_new ()
{
	DownloadNode*  dnode = g_malloc0 (sizeof (DownloadNode));

	// initialize base class
	base_node_instance_init (BASE_NODE (dnode));
	dnode->finalize = (BaseNodeFinalizeFunc) download_node_finalize;

	g_static_rec_mutex_init (&dnode->mutex);
//	dnode->thread      = NULL;
//	dnode->sync_code   = 0;
//	dnode->proxy_mode  = 0;
//	dnode->proxy_nth   = 0;

	dnode->state       = DOWNLOAD_STATE_WAITING;
//	dnode->url         = NULL;
//	dnode->filename    = NULL;
//	dnode->directory   = NULL;
//	dnode->referer     = NULL;

//	dnode->login       = 0;
//	dnode->username    = NULL;
//	dnode->password    = NULL;

	dnode->start_mode  = DOWNLOAD_START_AUTO;
	dnode->split_max   = 1;

	dnode->redirect_max  = 10;
	dnode->retry_max     = 99;
	dnode->retry_delay   = 3;

	// infomation while downloading --------------
//	dnode->redirect_times = 0;
//	dnode->retry_times    = 0;

	dnode->resumable      = TRUE;
//	dnode->completed_size = 0;
//	dnode->total_size     = 0;
//	dnode->percent        = 0;
//	dnode->speed          = 0;
//	dnode->message        = NULL;

	return dnode;
}

void  download_node_finalize (DownloadNode* dnode)
{
	g_static_rec_mutex_free (&dnode->mutex);
	g_free (dnode->url);
	g_free (dnode->filename);
	g_free (dnode->directory);
	g_free (dnode->referer);

	g_free (dnode->username);
	g_free (dnode->password);

	g_free (dnode->message);

	// finalize base class
	base_node_instance_finalize (BASE_NODE (dnode));
}

void download_node_prepend_proxy (DownloadNode* dnode, ProxyNode* pnode)
{
	download_node_lock (dnode);
	base_node_prepend (BASE_NODE (dnode), BASE_NODE (pnode));
	download_node_unlock (dnode);
}

void download_node_append_proxy (DownloadNode* dnode, ProxyNode* pnode)
{
	download_node_lock (dnode);
	base_node_append (BASE_NODE (dnode), BASE_NODE (pnode));
	download_node_unlock (dnode);
}

void download_node_remove_proxy (DownloadNode* dnode, ProxyNode* pnode)
{
	download_node_lock (dnode);
	if ((BaseNode*)dnode == base_node_parent (BASE_NODE (pnode)))
		base_node_unlink (BASE_NODE (pnode));
	download_node_unlock (dnode);
}

void download_node_insert_before (DownloadNode* dnode, ProxyNode* sibling, ProxyNode* pnode)
{
	download_node_lock (dnode);
	base_node_insert_before (BASE_NODE (dnode), BASE_NODE (sibling), BASE_NODE (pnode));
	download_node_unlock (dnode);
}

void download_node_insert_after (DownloadNode* dnode, ProxyNode* sibling, ProxyNode* pnode)
{
	download_node_lock (dnode);
	base_node_insert_after (BASE_NODE (dnode), BASE_NODE (sibling), BASE_NODE (pnode));
	download_node_unlock (dnode);
}

ProxyNode* download_node_pick_proxy (DownloadNode* dnode)
{
	ProxyNode*  pnode = NULL;

	if (dnode->proxy_mode == DOWNLOAD_PROXY_NONE)
		return NULL;

	download_node_lock (dnode);

	if (dnode->proxy_mode == DOWNLOAD_PROXY_TRY_ALL) {
		pnode = (ProxyNode*)base_node_nth_child (BASE_NODE (dnode), dnode->proxy_nth);
		dnode->proxy_nth++;
	}
	if (pnode == NULL) {
		pnode = (ProxyNode*)base_node_first_child (BASE_NODE (dnode));
		dnode->proxy_nth = 1;
	}

	download_node_unlock (dnode);

	return pnode;
}

void download_node_set_start_mode (DownloadNode* dnode, DownloadNodeStartMode m)
{
	dnode->start_mode = m;

	if (dnode->state == DOWNLOAD_STATE_WAITING  ||
	    dnode->state == DOWNLOAD_STATE_STOP     ||
	    dnode->state == DOWNLOAD_STATE_SCHEDULE)
	{
		if (m == DOWNLOAD_START_AUTO)
			dnode->state = DOWNLOAD_STATE_WAITING;
		if (m == DOWNLOAD_START_MANUAL)
			dnode->state = DOWNLOAD_STATE_STOP;
		if (m == DOWNLOAD_START_SCHEDULE)
			dnode->state = DOWNLOAD_STATE_SCHEDULE;
	}
}

void download_node_set_url (DownloadNode* node, const char* url_string)
{
	UrlInfo  url_info;
	char*    url;
	GString* gstr;

	download_node_lock (node);

    url  = strndup_no_crlf (url_string, -1);
	url_info_part (&url_info, url);

	// if node->filename was empty, set it.
	if (node->filename==NULL)
		str_replace_no_crlf (&node->filename, url_info.filename, url_info.filename_len);

	// if URL contain username, replace username and password in this node.
	if (url_info.id) {
		node->login = TRUE;
		str_replace_no_crlf (&node->username, url_info.id, url_info.id_len);
		str_replace_no_crlf (&node->password, url_info.password, url_info.password_len);

		// set URL without username and password
		gstr = g_string_sized_new (64);
		g_string_append_len (gstr, url_info.protocol, url_info.id - url_info.protocol);
		if (url_info.address)
			g_string_append (gstr, url_info.address);
		g_free (node->url);
		node->url = gstr->str;
		g_string_free (gstr, FALSE);
	} else {
		// copy URL directly
		str_replace_no_crlf (&node->url, url, -1);
	}

	download_node_unlock (node);
	g_free (url);
}

void download_node_set_directory (DownloadNode* node, const char* d)
{
	download_node_lock (node);
	str_replace_no_crlf (&node->directory, d, -1);
	download_node_unlock (node);
}

void download_node_set_filename (DownloadNode* node, const char* f)
{
	download_node_lock (node);
	str_replace_no_crlf (&node->filename, f, -1);
	download_node_unlock (node);
}

void download_node_set_referer (DownloadNode* node, const char* r)
{
	download_node_lock (node);
	str_replace_no_crlf (&node->referer, r, -1);
	download_node_unlock (node);
}

void download_node_set_username (DownloadNode* node, const char* u)
{
	download_node_lock (node);
	str_replace_no_crlf (&node->username, u, -1);
	download_node_unlock (node);
}

void download_node_set_password (DownloadNode* node, const char* p)
{
	download_node_lock (node);
	str_replace_no_crlf (&node->password, p, -1);
	download_node_unlock (node);
}

void download_node_set_message (DownloadNode* node, const char* m)
{
	download_node_lock (node);
	str_replace (&node->message, m, -1);
	download_node_unlock (node);
}

void download_node_apply (DownloadNode* dest, DownloadNode* src, DownloadApplyFlag flag)
{
	download_node_lock (dest);
	if (flag & DOWNLOAD_APPLY_LOGIN) {
		dest->login = src->login;
		str_replace_no_crlf (&dest->username, src->username, -1);
		str_replace_no_crlf (&dest->password, src->password, -1);
	}
	if (flag & DOWNLOAD_APPLY_PROXY)
		download_node_assign_proxy (dest, src);
	if (flag & DOWNLOAD_APPLY_REFERER)
		str_replace_no_crlf (&dest->referer, src->referer, -1);

	download_node_set_start_mode (dest, src->start_mode);
	dest->split_max    = src->split_max;
	dest->redirect_max = src->redirect_max;
	dest->retry_max    = src->retry_max;
	dest->retry_delay  = src->retry_delay;
	str_replace_no_crlf (&dest->directory, src->directory, -1);
	download_node_unlock (dest);
}

void download_node_assign (DownloadNode* dest, DownloadNode* src)
{
	download_node_lock (dest);
	download_node_apply (dest, src, DOWNLOAD_APPLY_ALL);
	str_replace (&dest->url, src->url, -1);
	str_replace (&dest->filename, src->filename, -1);
	dest->state = src->state;
	download_node_unlock (dest);
}

void download_node_assign_proxy (DownloadNode* dest, DownloadNode* src)
{
	ProxyNode* pnode;
	ProxyNode* pnode_temp;

	download_node_lock (dest);
	for (pnode = download_node_first_proxy (dest); pnode; pnode=pnode_temp) {
		pnode_temp = proxy_node_next (pnode);
		proxy_node_unlink (pnode);
		proxy_node_unref (pnode);
	}

	for (pnode = download_node_last_proxy (src); pnode; pnode=proxy_node_prev (pnode)) {
		pnode_temp = proxy_node_new ();
		proxy_node_assign (pnode_temp, pnode);
		download_node_prepend_proxy (dest, pnode_temp);
	}

	download_node_unlock (dest);
}

int   download_node_start (DownloadNode* dnode)
{
	dnode->state = DOWNLOAD_STATE_WAITING;
	return TRUE;
}

void  download_node_stop  (DownloadNode* dnode)
{
	if (dnode->state != DOWNLOAD_STATE_COMPLETED &&
	    dnode->state != DOWNLOAD_STATE_ERROR )
	{
		dnode->state = DOWNLOAD_STATE_STOP;
	}
}


// config writer & parser --------------------------------------------

void  download_node_write_conf (DownloadNode* dnode, ConfWriter* cw)
{
	ProxyNode* pnode;
	int  state = dnode->state;

	if (state == DOWNLOAD_STATE_RETRY || state == DOWNLOAD_STATE_ACTIVE)
		state = DOWNLOAD_STATE_WAITING;

	download_node_lock (dnode);

	conf_writer_start_element (cw, DOWNLOAD_NODE_TAG
	         " state=\"%d\""
	         " split_max=\"%d\""
	         " redirect_max=\"%d\""
			 " retry_max=\"%d\""
			 " retry_delay=\"%d\""
	         " start_mode=\"%d\""
	         " login=\"%d\""
			 " proxy_mode=\"%d\""
	         " total_size=\"%.0f\"",    // End of format string
	         state,
	         dnode->split_max,
	         dnode->redirect_max,
	         dnode->retry_max,
	         dnode->retry_delay,
	         dnode->start_mode,
	         dnode->login,
			 dnode->proxy_mode,
	         dnode->total_size
	         );

	// write proxy node
	pnode = (ProxyNode*)base_node_last_child (BASE_NODE (dnode));
	while (pnode) {
		proxy_node_write_conf (pnode, cw);
		pnode = (ProxyNode*) base_node_prev (BASE_NODE (pnode));
	}

	if (dnode->username) {
		conf_writer_start_element (cw, "username");
		conf_writer_text (cw, dnode->username);
		conf_writer_end_element (cw, "username");
	}

	if (dnode->password) {
		conf_writer_start_element (cw, "password");
		conf_writer_text (cw, dnode->password);
		conf_writer_end_element (cw, "password");
	}

	if (dnode->url) {
		conf_writer_start_element (cw, "URL");
		conf_writer_text (cw, dnode->url);
		conf_writer_end_element (cw, "URL");
	}

	if (dnode->directory) {
		conf_writer_start_element (cw, "directory");
		conf_writer_text (cw, dnode->directory);
		conf_writer_end_element (cw, "directory");
	}

	if (dnode->filename) {
		conf_writer_start_element (cw, "filename");
		conf_writer_text (cw, dnode->filename);
		conf_writer_end_element (cw, "filename");
	}

	if (dnode->referer) {
		conf_writer_start_element (cw, "referer");
		conf_writer_text (cw, dnode->referer);
		conf_writer_end_element (cw, "referer");
	}

	conf_writer_end_element (cw, DOWNLOAD_NODE_TAG);

	download_node_unlock (dnode);
}

// parser

void download_node_parser_attr (const gchar**     attr_names,
                                const gchar**     attr_values,
                                DownloadNode*     dnode)
{
	int ii;

	for (ii=0; attr_names[ii]; ii++) {
		if (strcmp (attr_names[ii], "start_mode")==0)
			dnode->start_mode = atoi (attr_values[ii]);
		else if (strcmp (attr_names[ii], "state")==0)
			dnode->state = atoi (attr_values[ii]);
		else if (strcmp (attr_names[ii], "proxy_mode")==0)
			dnode->proxy_mode = atoi (attr_values[ii]);
		else if (strcmp (attr_names[ii], "total_size")==0)
			dnode->total_size = atof (attr_values[ii]);
		else if (strcmp (attr_names[ii], "split_max")==0)
			dnode->split_max = atoi (attr_values[ii]);
		else if (strcmp (attr_names[ii], "redirect_max")==0)
			dnode->redirect_max = atoi (attr_values[ii]);
		else if (strcmp (attr_names[ii], "retry_max")==0)
			dnode->retry_max = atoi (attr_values[ii]);
		else if (strcmp (attr_names[ii], "retry_delay")==0)
			dnode->retry_delay = atoi (attr_values[ii]);
		else if (strcmp (attr_names[ii], "login")==0)
			dnode->login = atoi (attr_values[ii]);
	}
}

void download_node_parser_start_element (GMarkupParseContext* gmpc,
                                         const gchar*         element_name,
                                         const gchar**        attr_names,
                                         const gchar**        attr_values,
                                         gpointer             cparser,
                                         GError**             error)
{
	DownloadNode*  dnode = CONF_PARSER_DATA (cparser);
	ProxyNode*     pnode;

	if (strcmp (element_name, PROXY_NODE_TAG)==0) {
		pnode = proxy_node_new ();
		proxy_node_parser_attr (attr_names, attr_values, pnode);
		download_node_prepend_proxy (dnode, pnode);
	} else if (strcmp (element_name, "username")==0) {
		conf_parser_push_text_func_no_crlf (cparser, &dnode->username);
	} else if (strcmp (element_name, "password")==0) {
		conf_parser_push_text_func_no_crlf (cparser, &dnode->password);
	} else if (strcmp (element_name, "URL")==0) {
		conf_parser_push_text_func_no_crlf (cparser, &dnode->url);
	} else if (strcmp (element_name, "directory")==0) {
		conf_parser_push_text_func_no_crlf (cparser, &dnode->directory);
	} else if (strcmp (element_name, "filename")==0) {
		conf_parser_push_text_func_no_crlf (cparser, &dnode->filename);
	} else if (strcmp (element_name, "referer")==0) {
		conf_parser_push_text_func_no_crlf (cparser, &dnode->referer);
	} else if (strcmp (element_name, DOWNLOAD_NODE_TAG)==0) {
		download_node_parser_attr (attr_names, attr_values, dnode);
	}
}

void download_node_parser_end_element (GMarkupParseContext*  gmpc,
                                       const gchar*  element_name,
                                       gpointer      cparser,
                                       GError**      error)
{
	if (strcmp (element_name, DOWNLOAD_NODE_TAG)==0) {
		conf_parser_pop (cparser);
	} else if (strcmp (element_name, "username")==0) {
		conf_parser_pop_text_func (cparser);
	} else if (strcmp (element_name, "password")==0) {
		conf_parser_pop_text_func (cparser);
	} else if (strcmp (element_name, "URL")==0) {
		conf_parser_pop_text_func (cparser);
	} else if (strcmp (element_name, "directory")==0) {
		conf_parser_pop_text_func (cparser);
	} else if (strcmp (element_name, "filename")==0) {
		conf_parser_pop_text_func (cparser);
	} else if (strcmp (element_name, "referer")==0) {
		conf_parser_pop_text_func (cparser);
	}
}

const GMarkupParser download_node_parser = {
	download_node_parser_start_element,
	download_node_parser_end_element,
	NULL,
	NULL,
	NULL
};

